﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.Runtime.Serialization;

using NuMetaheuristics;
using NuMetaheuristics.Genotypes;
using NuMetaheuristics.Operators.Vector;

using MathNet.Numerics.Random;

namespace NuMetaheuristics.Algorithms
{
    /// <summary>
    /// The basic particle swarm algorithm maintains a "swarm" of candidate 
    /// solutions, modeled as particles having a position in n-dimensional 
    /// space (for this reason solutions must n-dimensional vectors). 
    /// Particles are moved about in space according to their velocity vector, 
    /// which is updated according to the globally best know position and the 
    /// particle's own best known position.;
    /// </summary>
    /// <typeparam name="G">The genotype class which inherits from IGenotype.</typeparam>
    [DataContract(Name = "ParticleSwarmAlgorithmOf{0}",
        Namespace = "http://numetaheuristics.codeplex.com/schemas")]
    [Export(typeof(IAlgorithm<>))]
    public class ParticleSwarmAlgorithm<G> : IAlgorithm<G>
        where G: IGenotype
    {
        private class Particle : IComparable<Particle>, ICloneable
        {
            public G Position { get; set; }
            public G Velocity { get; set; }
            public G PersonalBest { get; set; }

            public Particle() { }

            public Particle(G position, IContext<G> context,
                IUnaryOperator<G> velocityInitializer)
            {
                Position = (G)position.Clone();
                PersonalBest = (G)Position.Clone();

                var v = context.Produce(false, false, false);
                velocityInitializer.Operate(ref v);
                Velocity = v;
            }

            public int CompareTo(Particle other)
            {
                return Position.CompareTo(other.Position);
            }

            public object Clone()
            {
                Particle a = new Particle();
                a.Position = (G)Position.Clone();
                a.Velocity = (G)Velocity.Clone();
                a.PersonalBest = (G)PersonalBest.Clone();

                return a;
            }
        }

        [DataMember]
        private IUnaryOperator<G> _velocityInitializer;
        [DataMember]
        private IUnaryOperator<G> _velocityMutator;
        [DataMember]
        private IUnaryOperator<G> _alphaMultiplier;
        [DataMember]
        private IUnaryOperator<G> _betaMultiplier;
        [DataMember]
        private IUnaryOperator<G> _gammaMultiplier;
        [DataMember]
        private IUnaryOperator<G> _deltaMultiplier;
        [DataMember]
        private IUnaryOperator<G> _epsilonMultiplier;
        [DataMember]
        private IBinaryOperator<G> _adder;
        [DataMember]
        private IBinaryOperator<G> _subtracter;

        /// <summary>
        /// The default, no-argument constructor.
        /// </summary>
        public ParticleSwarmAlgorithm()
        {
        }


        /// <summary>
        /// The complete constructor which initializes all properties.
        /// </summary>
        /// <param name="positionBounder">This operator keeps position vectors within the desired bounds.</param>
        /// <param name="velocityInitializer">Used to provide intial values for velocity vectors.</param>
        /// <param name="velocityMutator">Used to modify velocity vectors slightly, providing a random element to each iteration.</param>
        /// <param name="alphaMultiplier">Multiplies a vector by a scalar randomly selected between zero and alpha.</param>
        /// <param name="betaMultiplier">Multiplies a vector by a scalar randomly selected between zero and beta.</param>
        /// <param name="gammaMultiplier">Multiplies a vector by a scalar randomly selected between zero and gamma.</param>
        /// <param name="deltaMultiplier">Multiplies a vector by a scalar randomly selected between zero and delta.</param>
        /// <param name="epsilonMultiplier">Multiplies a vector by a scalar randomly selected between zero and epsilon.</param>
        /// <param name="adder">Produce a new vector that is the sum of two given vectors</param>
        /// <param name="subtracter">Produce a new vector that is the difference between two given vectors</param>
        public ParticleSwarmAlgorithm(
            IUnaryOperator<G> velocityInitializer, 
            IUnaryOperator<G> velocityMutator,
            IUnaryOperator<G> alphaMultiplier,
            IUnaryOperator<G> betaMultiplier,
            IUnaryOperator<G> gammaMultiplier,
            IUnaryOperator<G> deltaMultiplier,
            IUnaryOperator<G> epsilonMultiplier,
            IBinaryOperator<G> adder,
            IBinaryOperator<G> subtracter
            )
        {
            _velocityMutator = velocityMutator;
            _velocityInitializer = velocityInitializer;
            _alphaMultiplier = alphaMultiplier;
            _betaMultiplier = betaMultiplier;
            _gammaMultiplier = gammaMultiplier;
            _deltaMultiplier = deltaMultiplier;
            _epsilonMultiplier = deltaMultiplier;

            _adder = adder;
            _subtracter = subtracter;
        }

        /// <summary>
        /// Run the algorithm against the given population, using the given 
        /// context, for a given number of iterations. Assumes the population 
        /// has already been evaluated and sorted.
        /// </summary>
        /// <param name="population">The population of candidate solutions to 
        /// operate on.</param>
        /// <param name="context">The context, containing all of the tools 
        /// necessary to work within the desired problem space</param>
        /// <param name="iterations">The number of iterations to run the 
        /// algorithm for.</param>
        public void Run(Population<G> population, IContext<G> context, uint iterations)
        {
            List<Particle> particles = new List<Particle>();
            
            foreach (G genome in population.Genomes)
            {
                particles.Add(new Particle(genome, context, _velocityInitializer));
            }

            for (uint i = 0; i < iterations; i++)
            {
                Particle best = (Particle)particles.Last().Clone();

                foreach (Particle particle in particles)
                {
                    var v_a = (G) particle.Velocity.Clone();
                    _alphaMultiplier.Operate(ref v_a);

                    var x_b = _subtracter.Operate(particle.PersonalBest, particle.Position);
                    _betaMultiplier.Operate(ref x_b);

                    //var x_c = _subtracter.Operate(particle.InformantBest, particle.Position);
                    //_gammaMultiplier.Operate(ref x_c);

                    var x_d = _subtracter.Operate(best.Position, particle.Position);
                    _deltaMultiplier.Operate(ref x_d);

                    v_a = _adder.Operate(v_a, x_b);
                    v_a = _adder.Operate(v_a, x_d);
                    _velocityMutator.Operate(ref v_a); 
                    v_a.CopyTo(particle.Velocity);
                }

                //mutate each particle according to its velocity vector
                foreach (Particle particle in particles)
                {
                    var v = (G) particle.Velocity.Clone();
                    _epsilonMultiplier.Operate(ref v);

                    var p = _adder.Operate(particle.Position, v);
                    
                    if(!context.IsValid(p))
                    {
                        context.MakeValid(ref p);
                    }

                    context.Evaluate(ref p);

                    p.CopyTo(particle.Position);

                    if (particle.Position.Fitness > particle.PersonalBest.Fitness)
                    {
                        particle.PersonalBest = (G)particle.Position.Clone();
                    }
                }
            }

            for (int i = 0; i < particles.Count(); i++)
            {
                particles[i].Position.CopyTo(population.Genomes[i]);
            }
        }
    }
}
